package AG;

import java.util.ArrayList;
import java.util.Random;

import Herramientas.Configuracion;
import Herramientas.Funciones;
import Herramientas.Restricciones;
import Herramientas.Resultado;
import Herramientas.dupla;
import Herramientas.par;

public class CromosomaPrograma extends Cromosoma {

	private double aptitud;
	private Arbol arbol;
	private int hMaxima;
	private final String[] cjtoTerms = { "CP", "BS", "SN" };
	private final String[] cjtoFuns = { "MP", "MM", "DU", "NOT", "EQ" };
	public final int numFun = 5;
	public final int numTerm = 3;
	public final int profMax = 4;

	public CromosomaPrograma() {
		// cargaConfiguraciones();
	}

	public double getAptitud() {
		return aptitud;
	}

	public void setAptitud(double aptitud) {
		this.aptitud = aptitud;
	}

	public Arbol getArbol() {
		return arbol;
	}

	public void setArbol(Arbol arbol) {
		this.arbol = arbol;
	}

	public int getHMaxima() {
		return hMaxima;
	}

	public void setHMaxima(int maxima) {
		hMaxima = maxima;
	}

	public String[] getCjtoTerms() {
		return cjtoTerms;
	}

	public String[] getCjtoFuns() {
		return cjtoFuns;
	}

	public void inicializarCompleta() {

		arbol = new Arbol();
		arbol.inicializarCompleta(0, cjtoTerms, cjtoFuns, 3);

	}

	public void inicializarCreciente() {
		arbol = new Arbol();
		arbol.inicializarCreciente(0, cjtoTerms, cjtoFuns, 6);

	}

	private void imprimeArbol(Arbol t) {
		// System.out.println(t);
		System.out.print(t);
	}

	@Override
	public Cromosoma copia() {
		CromosomaPrograma cp = new CromosomaPrograma();
		cp.setAptitud(this.aptitud);
		cp.setFenotipo(this.getFenotipo());
		cp.setGenes(this.getGenes());
		cp.setHMaxima(this.getHMaxima());
		cp.setPuntuacion(this.getPuntuacion());
		cp.setPuntuacion_acumulada(this.getPuntuacion_acumulada());
		 cp.setArbol(arbol.copia());
		//cp.setArbol(arbol);
		return cp;
	}

	@Override
	public int dameGen(int valor) {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public int getNumGenes() {
		// TODO Auto-generated method stub
		return 0;
	}

	@Override
	public void inicializarCromosoma(int opcion) {
		// TODO Auto-generated method stub
		if (opcion == 1) {
			this.inicializarCompleta();
		} else
			this.inicializarCreciente();
	}

	@Override
	public int compareTo(Object o) {
		// TODO Auto-generated method stub
		if (this.getPuntuacion() > ((CromosomaPrograma) o).getPuntuacion())
			return -1;
		if (this.getPuntuacion() == ((CromosomaPrograma) o).getPuntuacion())
			return 0;
		return 1;

	}

	public String fenotipo() {
		return this.toString();
	}

	@Override
	public double evaluar() {
		// Para evaluar probaremos cuantas de las posibles configuraciones
		// resultan satisfactorias
		int posiblesConfiguraciones = 160;
		int configuracionesBuenas = 0;
		ArrayList<Configuracion> configuraciones = new Configuracion()
				.dameConfiguraciones();

		for (int i = 0; i < posiblesConfiguraciones; i++) {
			Configuracion c = configuraciones.get(i);
			// En c tenemso la configuracion Cargada
			Resultado r1 = Funciones.ejecutaFuncion(this.getArbol().getValor(),
					this.getArbol(), c);
			if (r1.getConf().esIdeal()) {
				configuracionesBuenas++;

			}

		}
		// System.out.println(" ");
		// System.out.println("IMPRESIONANTE " + configuracionesBuenas);
		return configuracionesBuenas / 160.0;
	}

	public static void main(String args[]) {

		for (int i = 0; i < 100; i++) {
			CromosomaPrograma crom = new CromosomaPrograma();

			crom.inicializarCompleta();

			crom.imprimeArbol(crom.getArbol());

			crom.evaluar();

		}
	}

	@Override
	public int getLongitudCromosoma() {
		// TODO Auto-generated method stub
		return 0;
	}

	public int getNumNodos() {
		return this.getArbol().getNumNodos();
	}

	/**
	 * devuelve un par con el punto de cruce del padre, que no puede se un
	 * terminal y con la parte del arbol del padre que pasa a la madre
	 * 
	 * @param t
	 *            arbol del padre
	 * @param puntoCruce
	 *            punto que queremos buscar dentro del arbol
	 * @return
	 */
	public static par dameElArbol(Arbol t, int puntoCruce) {
		if (puntoCruce == 0) { // ya lo he encontrado
			if ((t.isEsHoja())) {
				if (t.getPadre() != null) {
					// tiene padre, le devuelvo.
					par aux = new par();
					aux.setArbol(t.getPadre());
					aux.setH1(-2); // cambiar puntoCruce por el anterior
					return aux;
				} else {
					par aux = new par();
					aux.setArbol(t.getPadre());
					aux.setH1(-3); // es un arbol unico con un nodo. NO SE PUEDE
									// CRUZAR
					return aux;
				}
			} else {
				par aux = new par();
				aux.setArbol(t);
				aux.setH1(-1);
				return aux;
			}
		} else {
			puntoCruce--;
			if (t.isEsHoja()) {
				// no esta por aqui
				par aux = new par();
				aux.setArbol(null); // arbol null para indicar que es hoja y se vuelve para arriba
				aux.setH1(puntoCruce);
				return aux;
			}

			Arbol ar = t.getHijos()[0];
			par p = dameElArbol(ar, puntoCruce);
			if ((p.getArbol() == null) && (t.getNumHijos() > 1)) {
				// SE LO PASO AL OTRO HIJO
				Arbol ar1 = t.getHijos()[1];
				p = dameElArbol(ar1, p.getH1());
				if (p.getArbol() == null) {
					par aux = new par();
					aux.setArbol(null);
					aux.setH1(p.getH1());
					return aux;
				} else {
					// ESTA EN EL HIJO DERECHO
					return p;

				}
			} else {
				// Tengo que comprobar si no tengo m�s hijos
				if (p.getArbol() == null && (t.getNumHijos() == 1)) {
					par aux = new par();
					aux.setArbol(null);
					aux.setH1(p.getH1());
					return aux;
				} else {
					// LO ha encontrado en el hijo izquierdo
					return p;
				}

			}

		}

	}
	
	public static par dameElArbolJavi(Arbol t) {
		par p = null;
		if (t.isEsHoja()){
			// si es hoja, no lo vamos a reproducir, y salimos, poniendo en par.h1 = -1
			p = new par();
			p.setArbol(t);
			p.setH1(-1);
		}
		else{
			// miro el numero de hijos
			int numHijos = t.getNumHijos();
			if (numHijos==1){
				// el unico hijo que tiene
				p = new par();
				p.setArbol(t.getHijos()[0]);
				p.setH1(0); // es el primer hijo
			}
			else{
				// tiene dos hijos, uno de los dos.
				Random r1 = new Random();
				int hijoACruzar = r1.nextInt(2); // para decidir hijo izquierdo o derecho
				if (hijoACruzar==0){
					// hijo izquierdo
					p = new par();
					p.setArbol(t.getHijos()[0]);
					p.setH1(0); // hijo izquierdo
				}
				else{
					// hijo derecho
					p = new par();
					p.setArbol(t.getHijos()[1]);
					p.setH1(1); // hijo derecho
				}
			}
		}
		return p;
	}

	public static CromosomaPrograma muta(CromosomaPrograma cromosomaPrograma,
			int tipoMutacion, double pm) {
		// TODO Auto-generated method stub
		if (tipoMutacion == 2)
			return cromosomaPrograma.mutacionTerminal(pm);
		else if (tipoMutacion == 3)
			return cromosomaPrograma.mutacionFuncional(pm);
		else if (tipoMutacion == 1)
			//return cromosomaPrograma.mutacionArbol(pm);
			return cromosomaPrograma.mutacionArbolFran( pm);
		return null;
	}

	private CromosomaPrograma mutacionArbolFran(double pm) {
		// TODO Auto-generated method stub
		
		 this.getArbol().mutacionArbolFran(0, cjtoTerms, cjtoFuns, pm);
		 return this;
	}

	/**
	 * reproduce solo en el nivel uno del padre, con algo del nivel uno o dos de la madre.
	 * @param padre
	 * @param madre
	 * @param pc
	 * @return
	 */
	public static dupla reproduce(CromosomaPrograma padre,
			CromosomaPrograma madre, double pc) {
		/*int numNodosPadre = padre.getNumNodos();
		Random r1 = new Random();
		int puntoCrucePadre = r1.nextInt(numNodosPadre);
		par p = dameElArbol(padre.getArbol(), puntoCrucePadre);*/
		par p = dameElArbolJavi(padre.getArbol());
		Arbol cortePadre = p.getArbol();
		//System.out.println("     dameElArbolJavi bien con el hijo: " + p.getH1());
		if (p.getH1() == -1) {
			// nodo hoja. No se cruzan
			dupla noCambio = new dupla();
			noCambio.setHijo1(padre);
			noCambio.setHijo2(madre);
			return noCambio;
		}
		
		String func = cortePadre.getValor();
		String[] compatibles = Funciones.dameCompatibles(func);

		// p.getH1 ==       -1, no se ha podido cambiar,  => 0 hijo izquierdo, 1 hijo derecho
		dupla hijos = Funciones.cruzaJavi(padre, p.getH1(), cortePadre,
				madre, compatibles);
		//System.out.println("  hijos creados.");
		return hijos;
	}
	
	/* REPRODUCE QUE COGE UN NODO AL AZAR.
	 * public static dupla reproduce(CromosomaPrograma padre,
			CromosomaPrograma madre, double pc) {
		int numNodosPadre = padre.getNumNodos();
		Random r1 = new Random();
		int puntoCrucePadre = r1.nextInt(numNodosPadre);
		par p = dameElArbol(padre.getArbol(), puntoCrucePadre);
		System.out.println("     dameElArbol bien con puntoCrucePadre: " + puntoCrucePadre);
		if (p.getH1() == -2) {
			puntoCrucePadre--;
		}
		if (p.getH1() == -3) {
			dupla aux = new dupla();
			aux.setHijo1(padre);
			aux.setHijo2(madre);
			return aux;
		}
		Arbol cortePadre = p.getArbol();

		String func = cortePadre.getValor();
		String[] compatibles = Funciones.dameCompatibles(func);

		// devuelve el punto de Cruce de la madre.
		dupla hijos = Funciones.cruza(padre, puntoCrucePadre, cortePadre,
				madre, compatibles);
		System.out.println("  hijos creados.");
		return hijos;
	}*/
	
	private CromosomaPrograma mutacionTerminal(double pm) {
		Arbol t = this.getArbol();

		if (t.isEsHoja()) {
			// METER PROBABILIDAD
			Random r1 = new Random();
			double mipm = r1.nextInt(100);
			mipm = mipm / 100;
			if (mipm < pm) {
				Random r = new Random();
				int numero = r.nextInt(this.cjtoTerms.length);
				String termino = cjtoTerms[numero];
				t.setValor(termino);
				this.setArbol(t);
			}
			return this;
		} else {

			for (int i = 0; i < t.getHijos().length; i++) {
				this.getArbol().getHijos()[i].mutacionTerminal(cjtoTerms, pm);
			}
			return this;
		}

	}

	private CromosomaPrograma mutacionFuncional(double pm) {
		Arbol t = this.getArbol();

		if (t.isEsHoja()) {
			// no hago nada, porque los terminales no son funciones
			return this;
		} else {
			// si no es hoja es que es una funci�n
			// miro a ver si muto el nodo
			Random r1 = new Random();
			double mipm = r1.nextInt(100);
			mipm = mipm / 100;
			
			if (mipm < pm) {
				String funcion = t.getValor();
				if ((funcion.equals("MP")) || (funcion.equals("MM"))) {
					// solo puede ser MP o MM
					Random r2 = new Random();
					int num = r2.nextInt(2); // para que me d� o 0 o 1
					String termino = cjtoFuns[num];
					t.setValor(termino);
					this.setArbol(t);
				} else if (funcion.equals("DU")) {
					// puedo meter un DU o un EQ -> 2 o 4
					Random r2 = new Random();
					int num = r2.nextInt(2); // para que me d� o 0 o 1
					if (num == 0) {
						num = 2; // DU
					} else {
						num = 4; // EQ
					}
					String termino = cjtoFuns[num];
					t.setValor(termino);
					this.setArbol(t);
				} else if (funcion.equals("EQ")) {
					// puede ser eq, y si est� bien el orden y dem�s, tb DU
					Arbol h1 = t.getHijos()[0];
					Arbol h2 = t.getHijos()[1];
					if ((h1.getPosSimbolo() < 3) && (h2.getPosSimbolo() > 2)) {
						// primero hay una accion y despues un predicado =>
						// puede haber DU
						Random r2 = new Random();
						int num = r2.nextInt(2); // para que me d� o 0 o 1
						if (num == 0) {
							num = 2; // DU
						} else {
							num = 4; // EQ
						}
						String termino = cjtoFuns[num];
						t.setValor(termino);
						this.setArbol(t);

					} else {
						// no puede haber DU, solo EQ. Como es lo que hay, no
						// hago nada
					}
				} else if (funcion.equals("NOT")) {
					// creo que nada, pero me has dicho algo del ultimo nivel de
					// la inicializaci�n creciente
					// SOLO SI HAY UN TERMINAL PONER MM,MP O NOT
				}
				
			}

			// llamo a la mutacion de sus hijos
			for (int i = 0; i < t.getHijos().length; i++) {
				this.getArbol().getHijos()[i].mutacionFuncional(cjtoFuns, pm);
			}
			return this;
		}

	}

	private CromosomaPrograma mutacionArbol(double pm) {
		Arbol t = this.getArbol().copia();
		int prof = t.getProfundidad();

		Random r1 = new Random();
		double mipm = r1.nextInt(100);
		mipm = mipm / 100.0;
		
		if (mipm < pm) {
			if (t.getPadre() != null) {
				int restriccion = Restricciones.dameRestriccionesCompleta(t	.getPadre().getValor());
				t.inicializarCompleta(prof, cjtoTerms, cjtoFuns, restriccion);
			} else {
				// nodo raiz, no hay restricciones
				t.inicializarCompleta(prof, cjtoTerms, cjtoFuns, 3);
			}
			this.setArbol(t);
		}

		// mutacion de los hijos
		else {
			if (this.getArbol().isEsHoja() == false) {
				for (int i = 0; i < t.getHijos().length; i++) {
					this.getArbol().getHijos()[i].mutacionArbol(cjtoTerms,
							cjtoFuns, pm);
				}
			}
		}
		return this;

	}

	public static dupla reproduce(CromosomaPrograma cromosomaPrograma,
			CromosomaPrograma cromosomaPrograma2, ArrayList<Integer> cruces,
			double pc) {

		// TODO Auto-generated method stub
		dupla d = reproduce(cromosomaPrograma, cromosomaPrograma2, pc);
		return d;
	}

}
